﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace DZTC.UselessAppEver.Core
{
    
    /// <summary>
    /// Just another exerimental thing.
    /// TODO: refactor, add System.Double overloads for Increment and Report
    /// </summary>
    public class ProgressToken
    {
        private class DisposableToken : IDisposable
        {
            public delegate void Action();

            private bool Disposed { get; set; }
            private Action DisposeAction { get; set; }

            public void Dispose()
            {
                if (Disposed)
                    throw new ObjectDisposedException("DisposableToken");

                DisposeAction();
                Disposed = true;
            }

            public DisposableToken(Action disposeAction)
            {
                if (disposeAction == null) throw new ArgumentNullException("disposeAction");
                DisposeAction = disposeAction;
            }
        }

        internal class EmptyToken : ProgressToken
        {
            public override void Increment(int delta)
            {
            }

            public override void Report(int value)
            {
            }

            public override IDisposable Fork(double coefficient)
            {
                return new DisposableToken(() => { });
            }
        }

        private struct ProgressTokenContext
        {
            public double BaseValue { get; set; }
            public double Coefficient { get; set; }
        }

        public delegate void ProgressChangedHandler(int value, int delta);

        
        public const int MAX_VALUE = 100;
        public const int MIN_VALUE = 0;

        /// <summary>
        /// Empty progress token.
        /// </summary>
        public static ProgressToken None
        {
            get { return new EmptyToken(); }
        }

        public event ProgressChangedHandler ProgressChanged;

        private void OnProgressChanged(int value, int delta)
        {
            var handler = ProgressChanged;
            if (handler != null) handler(value, delta);
        }


        private int Round(double dbl)
        {
            return (int) Math.Round(dbl);
        }


        private int Floor(double dbl)
        {
            return (int)Math.Floor(dbl);
        }

        private double TotalValue { get; set; }
        private int LastReported { get; set; }

        private ProgressTokenContext Context
        {
            get { return Contexts.Peek(); }
        }

        /// <summary>
        /// BaseValue of this token.
        /// </summary>
        public int Value
        {
            get { return Round(TotalValue); }
        }

        private Stack<ProgressTokenContext> Contexts { get; set; }

        private bool ValueCheck(double value)
        {
            return Round(value) <= MAX_VALUE && Round(value) >= MIN_VALUE;
        }

        private void NotifyOnChanged()
        {
            var reportDelta = Floor(TotalValue - LastReported);
            if (reportDelta > 0)
            {
                OnProgressChanged(Value, reportDelta);
                LastReported = Floor(TotalValue);
            }
        }

        /// <summary>
        /// Increments current value by given delta.
        /// </summary>
        /// <param name="delta"></param>
        public virtual void Increment(int delta)
        {
            var newValue = TotalValue + delta*Context.Coefficient;

            if (!ValueCheck(newValue) || !ValueCheck(newValue))
            {
                throw new ArgumentException("delta");
            }

            TotalValue = newValue;
            NotifyOnChanged();
        }


        /// <summary>
        /// Sets current value to given.
        /// </summary>
        /// <param name="value"></param>
        public virtual void Report(int value)
        {
            var newValue = Context.BaseValue + value*Context.Coefficient;
         
            if (!ValueCheck(value) || !ValueCheck(newValue))
            {
                throw new ArgumentException("value");
            }

            TotalValue = newValue;
            NotifyOnChanged();
        }

        /// <summary>
        /// Usage:
        ///     
        ///     using(pt.Fork(0.1))
        ///     {   
        ///         ...
        ///     }
        /// 
        /// </summary>
        public virtual IDisposable Fork(double coefficient)
        {
            if (coefficient <= 0 || coefficient > 1)
            {
                throw new ArgumentException("coefficient");
            }

            Contexts.Push(new ProgressTokenContext
                {
                    BaseValue = TotalValue,
                    Coefficient = coefficient
                });

            return new DisposableToken(PopStack);
        }

        private void PopStack()
        {
            Contexts.Pop();
        }

        public ProgressToken()
        {
            Contexts = new Stack<ProgressTokenContext>();
            Contexts.Push(new ProgressTokenContext
                {
                    Coefficient = 1d,
                    BaseValue = 0
                });
        }
    }
}